"""
场馆数据验证工具
提供场馆创建、更新、时间段管理等的数据验证功能
"""
import re
from datetime import datetime, time
from decimal import Decimal, InvalidOperation

class VenueValidator:
    """场馆数据验证器"""
    
    # 有效的场馆类型
    VALID_VENUE_TYPES = ['basketball', 'football', 'tennis', 'badminton', 'volleyball', 'table_tennis', 'gym', 'pool', 'other']
    
    # 场馆名称最大长度
    MAX_NAME_LENGTH = 50
    
    # 场馆描述最大长度
    MAX_DESCRIPTION_LENGTH = 500
    
    # 地址最大长度
    MAX_ADDRESS_LENGTH = 200
    
    # 联系电话正则
    PHONE_PATTERN = re.compile(r'^1[3-9]\d{9}$')
    
    # 最大容量限制
    MAX_CAPACITY_LIMIT = 10000
    
    @classmethod
    def validate_venue_data(cls, data, is_update=False):
        """
        验证场馆数据
        
        Args:
            data: 要验证的数据字典
            is_update: 是否为更新操作
            
        Raises:
            ValueError: 当数据验证失败时
        """
        if not data or not isinstance(data, dict):
            raise ValueError("请求数据不能为空")
            
        errors = []
        
        # 场馆名称验证
        if not is_update or 'name' in data:
            name = data.get('name', '').strip()
            if not name:
                errors.append('场馆名称不能为空')
            elif len(name) > cls.MAX_NAME_LENGTH:
                errors.append(f'场馆名称长度不能超过{cls.MAX_NAME_LENGTH}个字符')
            elif re.search(r'[<>"\';]', name):
                errors.append('场馆名称包含非法字符')
        
        # 场馆类型验证
        if not is_update or 'type' in data:
            venue_type = data.get('type', '').strip()
            if not venue_type:
                errors.append('场馆类型不能为空')
            elif venue_type not in cls.VALID_VENUE_TYPES:
                errors.append(f'无效的场馆类型，支持的类型：{", ".join(cls.VALID_VENUE_TYPES)}')
        
        # 地址验证
        if not is_update or 'address' in data:
            address = data.get('address', '').strip()
            if not address:
                errors.append('场馆地址不能为空')
            elif len(address) > cls.MAX_ADDRESS_LENGTH:
                errors.append(f'场馆地址长度不能超过{cls.MAX_ADDRESS_LENGTH}个字符')
        
        # 位置信息验证
        if not is_update or 'location' in data:
            location = data.get('location', '').strip()
            if not location:
                errors.append('位置信息不能为空')
            elif len(location) > 100:
                errors.append('位置信息长度不能超过100个字符')
        
        # 经纬度验证
        if 'latitude' in data:
            latitude = data.get('latitude')
            if latitude is not None:
                try:
                    lat_float = float(latitude)
                    if not (-90 <= lat_float <= 90):
                        errors.append('纬度必须在-90到90之间')
                except (ValueError, TypeError):
                    errors.append('纬度必须为有效数字')
        
        if 'longitude' in data:
            longitude = data.get('longitude')
            if longitude is not None:
                try:
                    lng_float = float(longitude)
                    if not (-180 <= lng_float <= 180):
                        errors.append('经度必须在-180到180之间')
                except (ValueError, TypeError):
                    errors.append('经度必须为有效数字')
        
        # 描述验证
        if 'description' in data:
            description = data.get('description', '')
            if description and len(description) > cls.MAX_DESCRIPTION_LENGTH:
                errors.append(f'场馆描述长度不能超过{cls.MAX_DESCRIPTION_LENGTH}个字符')
        
        # 联系电话验证
        if 'contact_phone' in data or 'contactPhone' in data:
            phone = data.get('contact_phone') or data.get('contactPhone', '')
            if phone:
                phone = phone.strip()
                if not cls.PHONE_PATTERN.match(phone):
                    errors.append('联系电话格式不正确，请输入有效的中国大陆手机号')
        
        # 营业时间验证
        if 'open_time' in data or 'openTime' in data:
            open_time = data.get('open_time') or data.get('openTime', '')
            if open_time:
                if not cls._validate_time_format(open_time):
                    errors.append('营业时间格式不正确，请使用如"08:00-22:00"的格式')
        
        # 状态验证
        if 'status' in data:
            status = data.get('status')
            if status is not None:
                if not isinstance(status, int) or status not in [0, 1]:
                    errors.append('场馆状态必须为0（关闭）或1（开放）')
        
        # 图片验证
        if 'images' in data:
            images = data.get('images')
            if images is not None:
                if not isinstance(images, list):
                    errors.append('场馆图片必须为数组格式')
                elif len(images) > 20:
                    errors.append('场馆图片最多20张')
                else:
                    for i, image in enumerate(images):
                        if not isinstance(image, str) or not image.strip():
                            errors.append(f'第{i+1}张图片URL不能为空')
        
        # 最大容量验证
        if 'max_capacity' in data:
            max_capacity = data.get('max_capacity')
            if max_capacity is not None:
                if not isinstance(max_capacity, int) or max_capacity <= 0:
                    errors.append('最大容量必须为正整数')
                elif max_capacity > cls.MAX_CAPACITY_LIMIT:
                    errors.append(f'最大容量不能超过{cls.MAX_CAPACITY_LIMIT}人')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def validate_time_slot_data(cls, data, is_update=False):
        """
        验证时间段数据
        
        Args:
            data: 要验证的数据字典
            is_update: 是否为更新操作
            
        Raises:
            ValueError: 当数据验证失败时
        """
        if not data or not isinstance(data, dict):
            raise ValueError("请求数据不能为空")
            
        errors = []
        
        # 开始时间验证
        if not is_update or 'start_time' in data:
            start_time = data.get('start_time', '').strip()
            if not start_time:
                errors.append('开始时间不能为空')
            elif not cls._validate_time_string(start_time):
                errors.append('开始时间格式不正确，请使用HH:MM格式')
        
        # 结束时间验证
        if not is_update or 'end_time' in data:
            end_time = data.get('end_time', '').strip()
            if not end_time:
                errors.append('结束时间不能为空')
            elif not cls._validate_time_string(end_time):
                errors.append('结束时间格式不正确，请使用HH:MM格式')
        
        # 验证时间逻辑
        start_time_str = data.get('start_time', '').strip()
        end_time_str = data.get('end_time', '').strip()
        
        if start_time_str and end_time_str:
            try:
                start_time_obj = datetime.strptime(start_time_str, '%H:%M').time()
                end_time_obj = datetime.strptime(end_time_str, '%H:%M').time()
                
                if start_time_obj >= end_time_obj:
                    errors.append('开始时间必须早于结束时间')
                
                # 计算时间差，不能超过12小时
                start_minutes = start_time_obj.hour * 60 + start_time_obj.minute
                end_minutes = end_time_obj.hour * 60 + end_time_obj.minute
                
                # 处理跨夜情况
                if end_minutes < start_minutes:
                    end_minutes += 24 * 60
                
                duration_minutes = end_minutes - start_minutes
                if duration_minutes > 12 * 60:
                    errors.append('时间段不能超过12小时')
                elif duration_minutes < 30:
                    errors.append('时间段不能少于30分钟')
                    
            except ValueError:
                pass  # 时间格式错误已在上面检查
        
        # 容量验证
        if 'capacity' in data:
            capacity = data.get('capacity')
            if capacity is not None:
                if not isinstance(capacity, int) or capacity <= 0:
                    errors.append('时间段容量必须为正整数')
                elif capacity > cls.MAX_CAPACITY_LIMIT:
                    errors.append(f'时间段容量不能超过{cls.MAX_CAPACITY_LIMIT}人')
        
        # 价格验证
        if 'price' in data:
            price = data.get('price')
            if price is not None:
                try:
                    price_decimal = Decimal(str(price))
                    if price_decimal < 0:
                        errors.append('价格不能为负数')
                    elif price_decimal > Decimal('9999.99'):
                        errors.append('价格不能超过9999.99元')
                except (InvalidOperation, ValueError):
                    errors.append('价格必须为有效数字')
        
        # 每人价格验证
        if 'per_person_price' in data:
            per_person_price = data.get('per_person_price')
            if per_person_price is not None:
                try:
                    price_decimal = Decimal(str(per_person_price))
                    if price_decimal < 0:
                        errors.append('每人价格不能为负数')
                    elif price_decimal > Decimal('999.99'):
                        errors.append('每人价格不能超过999.99元')
                except (InvalidOperation, ValueError):
                    errors.append('每人价格必须为有效数字')
        
        # 最小收费人数验证
        if 'min_charge_count' in data:
            min_charge_count = data.get('min_charge_count')
            if min_charge_count is not None:
                if not isinstance(min_charge_count, int) or min_charge_count < 0:
                    errors.append('最小收费人数必须为非负整数')
                elif min_charge_count > 100:
                    errors.append('最小收费人数不能超过100人')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def validate_pricing_data(cls, data):
        """
        验证定价数据
        
        Args:
            data: 包含时间段定价信息的数据字典
            
        Raises:
            ValueError: 当数据验证失败时
        """
        if not data or not isinstance(data, dict):
            raise ValueError("请求数据不能为空")
        
        if 'time_slots' not in data:
            raise ValueError("缺少时间段数据")
        
        time_slots = data.get('time_slots')
        if not isinstance(time_slots, list):
            raise ValueError("时间段数据必须为数组格式")
        
        if not time_slots:
            raise ValueError("时间段数据不能为空")
        
        if len(time_slots) > 50:
            raise ValueError("批量更新时间段不能超过50个")
        
        errors = []
        time_slot_ids = set()
        
        for i, slot in enumerate(time_slots):
            if not isinstance(slot, dict):
                errors.append(f'第{i+1}个时间段数据格式错误')
                continue
            
            # 时间段ID验证
            time_slot_id = slot.get('time_slot_id')
            if not time_slot_id:
                errors.append(f'第{i+1}个时间段缺少时间段ID')
            elif not isinstance(time_slot_id, int) or time_slot_id <= 0:
                errors.append(f'第{i+1}个时间段ID必须为正整数')
            elif time_slot_id in time_slot_ids:
                errors.append(f'第{i+1}个时间段ID重复')
            else:
                time_slot_ids.add(time_slot_id)
            
            # 每人价格验证
            per_person_price = slot.get('per_person_price')
            if per_person_price is None:
                errors.append(f'第{i+1}个时间段缺少每人价格')
            else:
                try:
                    price_decimal = Decimal(str(per_person_price))
                    if price_decimal < 0:
                        errors.append(f'第{i+1}个时间段每人价格不能为负数')
                    elif price_decimal > Decimal('999.99'):
                        errors.append(f'第{i+1}个时间段每人价格不能超过999.99元')
                except (InvalidOperation, ValueError):
                    errors.append(f'第{i+1}个时间段每人价格必须为有效数字')
            
            # 最小收费人数验证（可选）
            if 'min_charge_count' in slot:
                min_charge_count = slot.get('min_charge_count')
                if min_charge_count is not None:
                    if not isinstance(min_charge_count, int) or min_charge_count < 0:
                        errors.append(f'第{i+1}个时间段最小收费人数必须为非负整数')
                    elif min_charge_count > 100:
                        errors.append(f'第{i+1}个时间段最小收费人数不能超过100人')
            
            # 容量验证（可选）
            if 'capacity' in slot:
                capacity = slot.get('capacity')
                if capacity is not None:
                    if not isinstance(capacity, int) or capacity <= 0:
                        errors.append(f'第{i+1}个时间段容量必须为正整数')
                    elif capacity > cls.MAX_CAPACITY_LIMIT:
                        errors.append(f'第{i+1}个时间段容量不能超过{cls.MAX_CAPACITY_LIMIT}人')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def validate_checkin_data(cls, data):
        """
        验证签到数据
        
        Args:
            data: 签到数据字典
            
        Raises:
            ValueError: 当数据验证失败时
        """
        if not data or not isinstance(data, dict):
            raise ValueError("请求数据不能为空")
        
        errors = []
        
        # 订单ID验证
        order_id = data.get('order_id')
        if not order_id:
            errors.append('订单ID不能为空')
        elif not isinstance(order_id, int) or order_id <= 0:
            errors.append('订单ID必须为正整数')
        
        # 实际到场人数验证
        actual_attendance = data.get('actual_attendance')
        if actual_attendance is None:
            errors.append('实际到场人数不能为空')
        elif not isinstance(actual_attendance, int) or actual_attendance < 0:
            errors.append('实际到场人数必须为非负整数')
        elif actual_attendance > 1000:
            errors.append('实际到场人数不能超过1000人')
        
        # 签到状态验证
        checkin_status = data.get('checkin_status')
        if checkin_status is None:
            errors.append('签到状态不能为空')
        elif not isinstance(checkin_status, int) or checkin_status not in [1, 2, 3]:
            errors.append('签到状态必须为1（已签到）、2（迟到）或3（缺席）')
        
        # 签到码验证（可选）
        if 'checkin_code' in data:
            checkin_code = data.get('checkin_code', '').strip()
            if checkin_code and len(checkin_code) > 20:
                errors.append('签到码长度不能超过20个字符')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def validate_search_params(cls, page, per_page, keyword):
        """
        验证搜索参数
        
        Args:
            page: 页码
            per_page: 每页数量
            keyword: 搜索关键词
            
        Raises:
            ValueError: 当参数验证失败时
        """
        errors = []
        
        # 页码验证
        if page is not None:
            if not isinstance(page, int) or page < 1:
                errors.append('页码必须为正整数')
            elif page > 10000:
                errors.append('页码不能超过10000')
        
        # 每页数量验证
        if per_page is not None:
            if not isinstance(per_page, int) or per_page < 1:
                errors.append('每页数量必须为正整数')
            elif per_page > 100:
                errors.append('每页数量不能超过100')
        
        # 关键词验证
        if keyword is not None:
            if len(keyword) > 50:
                errors.append('搜索关键词长度不能超过50个字符')
            # 检查是否包含特殊字符（防止注入）
            if re.search(r'[<>"\';]', keyword):
                errors.append('搜索关键词包含非法字符')
        
        if errors:
            raise ValueError('; '.join(errors))
    
    @classmethod
    def _validate_time_format(cls, time_str):
        """验证时间格式如 08:00-22:00"""
        pattern = r'^\d{2}:\d{2}-\d{2}:\d{2}$'
        return bool(re.match(pattern, time_str))
    
    @classmethod
    def _validate_time_string(cls, time_str):
        """验证时间字符串格式 HH:MM"""
        try:
            datetime.strptime(time_str, '%H:%M')
            return True
        except ValueError:
            return False 